/*
 * Created on Feb 16, 2005
 * Created by dfhuynh
 */
package edu.mit.simile.longwell.query.bucket;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.TreeSet;

import edu.mit.simile.longwell.Profile;
import edu.mit.simile.longwell.query.project.IIntegerProjection;
import edu.mit.simile.longwell.query.project.IProjection;
import edu.mit.simile.longwell.schema.ISchemaModel;

/**
 * @author dfhuynh
 */
public class IntegerBucketer extends BucketerBase {
	final static protected int	s_mode_exact = 0;
	final static protected int	s_mode_range = 1;
	final static protected int	s_mode_lessThan = 2;
	final static protected int	s_mode_lessThanOrEqual = 3;
	final static protected int	s_mode_greaterThan = 4;
	final static protected int	s_mode_greaterThanOrEqual = 5;
	
	final static protected String[] s_modePrefixes = new String[] {
		"==",
		"<>",
		"<<",
		"<=",
		">>",
		">="
	};
	
	/**
	 * @param profile
	 */
	public IntegerBucketer(Profile profile) {
		super(profile);
	}
	
	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.query.bucket.BucketerBase#matchesOneValue(java.lang.String)
	 */
	public boolean matchesOneValue(String parameter) {
		String[] params = splitParameter(parameter);
		if (params.length > 1) {
			return false;
		} else {
			return params[0].startsWith(s_modePrefixes[s_mode_exact]);
		}
	}

	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.query.bucket.BucketerBase#internalGetBucket(edu.mit.simile.longwell.query.project.IProjection, java.lang.String)
	 */
	protected Set internalGetBucket(IProjection projection, String parameter) {
		String[] params = splitParameter(parameter);
		Set objects = new HashSet();	
		
		for (int i = 0; i < params.length; i++) {
			internalGetOneBucket(projection, params[i], objects);
		}
		return objects;
	}
	
	protected void internalGetOneBucket(IProjection projection, String parameter, Set objects) {
		IIntegerProjection iProjection = (IIntegerProjection) projection;
		int[] params = parseParameter(parameter);
		
		Set objects2 = null;
		if (params[0] < s_modePrefixes.length) {
			switch (params[0]) {
			case s_mode_exact:
				objects2 = iProjection.getObjects(params[1], params[1] + 1);
				break;
			case s_mode_range:
				objects2 = iProjection.getObjects(params[1], params[2]);
				break;
			case s_mode_lessThan:
				objects2 = iProjection.getObjects(iProjection.getMin(), params[1]);
				break;
			case s_mode_lessThanOrEqual:
				objects2 = iProjection.getObjects(iProjection.getMin(), params[1] + 1);
				break;
			case s_mode_greaterThan:
				objects2 = iProjection.getObjects(params[1] + 1, iProjection.getMax());
				break;
			case s_mode_greaterThanOrEqual:
				objects2 = iProjection.getObjects(params[1], iProjection.getMax());
				break;
			}
		}
		if (objects2 != null) {
			objects.addAll(objects2);
		}
	}

	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.query.bucket.BucketerBase#internalSuggestBuckets(edu.mit.simile.longwell.query.project.IProjection, desirable)
	 */
	protected List internalSuggestNarrowingBuckets(IProjection projection, float desirable) {
		ISchemaModel 	schemaManager = m_profile.getSchemaModel();
		List 			bucketThemes = new ArrayList();
		
		Set values = projection.getValues();
		
		// Only 1 value, nothing to refine by
		if (desirable < 1 && values.size() < 2) {
			return bucketThemes;
		}
		
		// Few values, just refine by distinct values
		if (values.size() < 20) {
			suggestDistinctValues(projection, values, bucketThemes, null, "");
		} else {
			suggestRanges(projection, values, bucketThemes, null, "");
		}
		
		return bucketThemes;
	}
	
	static protected class IntegerBucket extends Bucket {
		final int m_value;
		
		public IntegerBucket(
				String	bucketerName,
				String 	bucketerParameter,
				String	label,
				int 	count,
				int		value) {
			super(bucketerName, bucketerParameter, label, count);
			m_value = value;
		}
	}
	static protected class IntegerBucketNarrowerComparator implements Comparator {
		/* (non-Javadoc)
		 * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
		 */
		public int compare(Object o1, Object o2) {
			IntegerBucket b1 = (IntegerBucket) o1;
			IntegerBucket b2 = (IntegerBucket) o2;

			return b1.m_value - b2.m_value;
		}
		
	}

	protected void suggestDistinctValues(
			IProjection projection, Set values, List bucketThemes, String[] excepts, String concatWith) {
		
		Set buckets = new TreeSet(new IntegerBucketNarrowerComparator());
		
		Iterator i = values.iterator();
		outer:while (i.hasNext()) {
			Object o = i.next();
			
			if (o instanceof Integer) {
				Integer integer = (Integer) o;
				
				String label = integer.toString();
				String parameter = s_modePrefixes[s_mode_exact] + label;
				
				if (excepts != null) {
					for (int n = 0; n < excepts.length; n++) {
						if (parameter.equals(excepts[n])) {
							continue outer;
						}
					}
				}
				
				buckets.add(
					new IntegerBucket(
						this.getClass().getName(), 
						concatWith + encodeParameter(parameter),
						label, 
						projection.countObjects(o),
						integer.intValue()));
			}
		}
		
		bucketThemes.add(new BucketTheme(
			"",
			"distinct-values",
			new ArrayList(buckets)
		));
	}
	
	protected void suggestRanges(
			IProjection projection, Set values, List bucketThemes, String[] excepts, String concatWith) {
		IIntegerProjection iProjection = (IIntegerProjection) projection;
		Set buckets = new TreeSet(new IntegerBucketNarrowerComparator());
		int min = iProjection.getMin();
		int max = iProjection.getMax();
		
		int diff = max - min;
		int interval = 10;
		
		while (diff / interval > 10) {
			interval *= 10;
		}
		if (diff / interval < 2) {
			interval /= 10;
		}
		
		min = (min / interval) * interval;
		max = (1 + max / interval) * interval;
		
		String format = getResources(projection.getLocale()).getString("RangeFormat");
		
		int intervalCount = (max - min) / interval;
		outer:for (int j = 0; j < intervalCount; j++) {
			int from = (min + j * interval);
			int to = (min + j * interval + interval - 1);
			int count = iProjection.countObjects(from, to);
			
			if (count > 0) {
				String label = MessageFormat.format(format, 
					new Object[] { new Integer(from), new Integer(to) });
				
				String parameter = 
					s_modePrefixes[s_mode_range] + from + "," + to;
				
				if (excepts != null) {
					for (int n = 0; n < excepts.length; n++) {
						if (parameter.equals(excepts[n])) {
							continue outer;
						}
					}
				}
				
				buckets.add(
					new IntegerBucket(
						this.getClass().getName(), 
						concatWith + encodeParameter(parameter),
						label, 
						count,
						from));
			}
		}
		
		bucketThemes.add(new BucketTheme(
			"",
			"distinct-values",
			new ArrayList(buckets)
		));
	}
	
	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.query.bucket.BucketerBase#internalSuggestBroadeningBuckets(edu.mit.simile.longwell.query.project.IProjection, java.lang.String)
	 */
	protected BroadeningResult internalSuggestBroadeningBuckets(
			IProjection projection, String parameter) {
		String			locale = projection.getLocale();
		String[] 		params = splitParameter(parameter);
		
		int mode = s_mode_exact;
		
		List existingBuckets = new ArrayList();
		
		for (int i = 0; i < params.length; i++) {
			String param = "";
			String label = "";
			
			for (int j = 0; j < params.length; j++) {
				if (i != j) {
					if (param.length() == 0) {
						param += encodeParameter(params[j]);
					} else {
						param += "," + encodeParameter(params[j]);
					}
				}
			}
			
			int[] ints = parseParameter(params[i]);
			if (ints[0] < s_modePrefixes.length) {
				mode = ints[0];
				existingBuckets.add(
					new Bucket(
						this.getClass().getName(), 
						param,
						paramToLabel(ints, locale),
						0));
			}
		}
		
		List bucketThemes = new ArrayList();
		
		IIntegerProjection iProjection = (IIntegerProjection) projection;
		Set values = iProjection.getValues();
		
		if (mode == s_mode_range) {
			suggestRanges(iProjection, values, bucketThemes, params, parameter + ",");
		} else {
			suggestDistinctValues(iProjection, values, bucketThemes, params, parameter + ",");
		}

		return new BroadeningResult(existingBuckets, bucketThemes);
	}
	
	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.query.bucket.BucketerBase#individualParameterToDescription(java.lang.String, java.lang.String)
	 */
	protected String individualParameterToDescription(
			String parameter,
			String locale) {
		return paramToLabel(parseParameter(parameter), locale);
	}
	
	protected int[] parseParameter(String parameter) {
		int mode = s_mode_exact;
		int[] results = null;
		
		if (parameter != null) {
			while (mode < s_modePrefixes.length) {
				if (parameter.startsWith(s_modePrefixes[mode])) {
					parameter = parameter.substring(
							s_modePrefixes[mode].length());
					break;
				}
				mode++;
			}
			
			String[] integers = parameter.split(",");
			
			results = new int[Math.max(2, integers.length + 1)];
			results[0] = mode;
			
			for (int i = 0; i < integers.length; i++) {
				try {
					results[i+1] = Integer.parseInt(integers[i]);
				} catch (NumberFormatException e) {
				}
			}
		}
		
		if (results == null) {
			results = new int[2];
			results[0] = mode;
		}
		
		return results;
	}
	
	protected String paramToLabel(int[] ints, String locale) {
		ResourceBundle resources = getResources(locale);
		String label = "";
		
		switch (ints[0]) {
		case s_mode_exact:
			label = MessageFormat.format(
				resources.getString("ExactTermFormat"), 
				new Object[] { new Integer(ints[1]) });
			break;
		case s_mode_range:
			label = MessageFormat.format(
				resources.getString("RangeTermFormat"), 
			new Object[] { new Integer(ints[1]), new Integer(ints[2]) });
			break;
		case s_mode_lessThan:
			label = MessageFormat.format(
				resources.getString("LessThanTermFormat"), 
				new Object[] { new Integer(ints[1]) });
			break;
		case s_mode_lessThanOrEqual:
			label = MessageFormat.format(
				resources.getString("LessThanOrEqualTermFormat"), 
				new Object[] { new Integer(ints[1]) });
			break;
		case s_mode_greaterThan:
			label = MessageFormat.format(
				resources.getString("GreaterThanTermFormat"), 
				new Object[] { new Integer(ints[1]) });
			break;
		case s_mode_greaterThanOrEqual:
			label = MessageFormat.format(
				resources.getString("GreaterThanOrEqualTermFormat"), 
				new Object[] { new Integer(ints[1]) });
			break;
		}
		
		return label;
	}
}
